{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Timeseries\n",
    "\n",
    "`redis-py` supports [RedisTimeSeries](https://github.com/RedisTimeSeries/RedisTimeSeries/) which is a time-series-database module for Redis.\n",
    "\n",
    "This example shows how to handle timeseries data with `redis-py`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Health check"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import redis \n",
    "\n",
    "r = redis.Redis(decode_responses=True)\n",
    "ts = r.ts()\n",
    "\n",
    "r.ping()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Simple example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create a timeseries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.create(\"ts_key\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add samples to the timeseries\n",
    "\n",
    "We can either set the timestamp with an UNIX timestamp in milliseconds or use * to set the timestamp based en server's clock."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1657272304448"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.add(\"ts_key\", 1657265437756, 1)\n",
    "ts.add(\"ts_key\", \"1657265437757\", 2)\n",
    "ts.add(\"ts_key\", \"*\", 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Get the last sample"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1657272304448, 3.0)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.get(\"ts_key\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Get samples between two timestamps\n",
    "\n",
    "The minimum and maximum possible timestamps can be expressed with respectfully - and +."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1657265437756, 1.0), (1657265437757, 2.0), (1657272304448, 3.0)]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.range(\"ts_key\", \"-\", \"+\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1657265437756, 1.0), (1657265437757, 2.0)]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.range(\"ts_key\", 1657265437756, 1657265437757)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Delete samples between two timestamps"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Before deletion:  [(1657265437756, 1.0), (1657265437757, 2.0), (1657272304448, 3.0)]\n",
      "After deletion:   [(1657272304448, 3.0)]\n"
     ]
    }
   ],
   "source": [
    "print(\"Before deletion: \", ts.range(\"ts_key\", \"-\", \"+\"))\n",
    "ts.delete(\"ts_key\", 1657265437756, 1657265437757)\n",
    "print(\"After deletion:  \", ts.range(\"ts_key\", \"-\", \"+\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Multiple timeseries with labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.create(\"ts_key1\")\n",
    "ts.create(\"ts_key2\", labels={\"label1\": 1, \"label2\": 2})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add samples to multiple timeseries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1657272306147, 1657272306147]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.madd([(\"ts_key1\", \"*\", 1), (\"ts_key2\", \"*\", 2)])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Add samples with labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1657272306457"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.add(\"ts_key2\", \"*\", 2,  labels={\"label1\": 1, \"label2\": 2})\n",
    "ts.add(\"ts_key2\", \"*\", 2,  labels={\"label1\": 3, \"label2\": 4})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Get the last sample matching specific label\n",
    "\n",
    "Get the last sample that matches \"label1=1\", see [Redis documentation](https://redis.io/commands/ts.mget/) to see the posible filter values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'ts_key2': [{}, 1657272306457, 2.0]}]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.mget([\"label1=1\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Get also the label-value pairs of the sample:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'ts_key2': [{'label1': '1', 'label2': '2'}, 1657272306457, 2.0]}]"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.mget([\"label1=1\"], with_labels=True)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Retention period\n",
    "\n",
    "You can specify a retention period when creating timeseries objects or when adding a sample timeseries object. Once the retention period has elapsed, the sample is removed from the timeseries."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "retention_time = 1000\n",
    "ts.create(\"ts_key_ret\", retention_msecs=retention_time)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Base timeseries:                      [(1657272307670, 1.0)]\n",
      "Timeseries after 1000 milliseconds:   [(1657272307670, 1.0)]\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "# this will be deleted in 1000 milliseconds\n",
    "ts.add(\"ts_key_ret\", \"*\", 1, retention_msecs=retention_time)\n",
    "print(\"Base timeseries:                     \", ts.range(\"ts_key_ret\", \"-\", \"+\"))\n",
    "# sleeping for 1000 milliseconds (1 second)\n",
    "time.sleep(1)\n",
    "print(\"Timeseries after 1000 milliseconds:  \", ts.range(\"ts_key_ret\", \"-\", \"+\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The two lists are the same, this is because the oldest values are deleted when a new sample is added."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1657272308849"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.add(\"ts_key_ret\", \"*\", 10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1657272308849, 10.0)]"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.range(\"ts_key_ret\", \"-\", \"+\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here the first sample has been deleted."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Specify duplicate policies\n",
    "\n",
    "By default, the policy for duplicates timestamp keys is set to \"BLOCK\", we cannot create two samples with the same timestamp:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "TSDB: Error at upsert, update is not supported when DUPLICATE_POLICY is set to BLOCK mode\n"
     ]
    }
   ],
   "source": [
    "ts.add(\"ts_key\", 123456789, 1)\n",
    "try:\n",
    "    ts.add(\"ts_key\", 123456789, 2)\n",
    "except Exception as err:\n",
    "    print(err)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can change this default behaviour using `duplicate_policy` parameter, for instance:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(123456789, 2.0), (1657272304448, 3.0)]"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# using policy \"LAST\", we keep the last added sample\n",
    "ts.add(\"ts_key\", 123456789, 2, duplicate_policy=\"LAST\")\n",
    "ts.range(\"ts_key\", \"-\", \"+\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For more informations about duplicate policies, see [Redis documentation](https://redis.io/commands/ts.add/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using Redis TSDB to keep track of a value"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "1657272310241"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.add(\"ts_key_incr\", \"*\", 0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Increment the value:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "for _ in range(10):\n",
    "    ts.incrby(\"ts_key_incr\", 1)\n",
    "    # sleeping a bit so the timestamp are not duplicates\n",
    "    time.sleep(0.01)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(1657272310241, 0.0),\n",
       " (1657272310533, 1.0),\n",
       " (1657272310545, 2.0),\n",
       " (1657272310556, 3.0),\n",
       " (1657272310567, 4.0),\n",
       " (1657272310578, 5.0),\n",
       " (1657272310589, 6.0),\n",
       " (1657272310600, 7.0),\n",
       " (1657272310611, 8.0),\n",
       " (1657272310622, 9.0),\n",
       " (1657272310632, 10.0)]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ts.range(\"ts_key_incr\", \"-\", \"+\")"
   ]
  },
  {
   "cell_type": "markdown",
   "source": [
    "## How to execute multi-key commands on Open Source Redis Cluster"
   ],
   "metadata": {
    "collapsed": false
   }
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "outputs": [
    {
     "data": {
      "text/plain": "[{'ts_key1': [{}, 1670927124746, 2.0]}, {'ts_key2': [{}, 1670927124748, 10.0]}]"
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import redis\n",
    "\n",
    "r = redis.RedisCluster(host=\"localhost\", port=46379)\n",
    "\n",
    "# This command should be executed on all cluster nodes after creation and any re-sharding\n",
    "# Please note that this command is internal and will be deprecated in the future\n",
    "r.execute_command(\"timeseries.REFRESHCLUSTER\", target_nodes=\"primaries\")\n",
    "\n",
    "# Now multi-key commands can be executed\n",
    "ts = r.ts()\n",
    "ts.add(\"ts_key1\", \"*\", 2,  labels={\"label1\": 1, \"label2\": 2})\n",
    "ts.add(\"ts_key2\", \"*\", 10,  labels={\"label1\": 1, \"label2\": 2})\n",
    "ts.mget([\"label1=1\"])"
   ],
   "metadata": {
    "collapsed": false
   }
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.9.2 64-bit",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.2"
  },
  "orig_nbformat": 4,
  "vscode": {
   "interpreter": {
    "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
